/**************************************************************************
 *
 * Copyright (C) 2007 University of California, Los Angeles.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 *************************************************************************/

/**
 * 
 */
package edu.ucla.cs.typecast.test.config;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.util.Date;
import java.util.StringTokenizer;

import edu.ucla.cs.typecast.net.EndPoint;
import edu.ucla.cs.typecast.net.NetUtil;
import edu.ucla.cs.typecast.test.AsyncPingAckServer;
import edu.ucla.cs.typecast.test.AsyncPingSender;
import edu.ucla.cs.typecast.test.DiscoverSender;
import edu.ucla.cs.typecast.test.PingAck;
import edu.ucla.cs.typecast.test.PingFuture;
import edu.ucla.cs.typecast.test.PingSender;
import edu.ucla.cs.typecast.test.PingSenderTypeCastAck;
import edu.ucla.cs.typecast.test.PingServer;
import edu.ucla.cs.typecast.test.PingServerExt;

/**
 *
 * @date Aug 26, 2007
 */
public class PingConfigParser {
	private OutputStream senderLogStream; 
	private OutputStream receiverLogStream; 
	private OutputStream asyncSenderStream; 
	private OutputStream asyncAckStream; 
	private OutputStream latencyLogStream; 
	
	private String logDir; 
	public void setLogDir(String dir) { 
		logDir = dir;  
	}
	
	public void parse(InputStream in) throws IOException { 
		String localHost = NetUtil.getLocalHost().getHostAddress(); 
		String senderLogFile = "PingSenderLog." + NetUtil.getLocalHost().getHostAddress(); 
		String serverLogFile = "PingServerLog." + NetUtil.getLocalHost().getHostAddress(); 
		String asyncSenderLogFile = "PingAsyncSenderLog." + NetUtil.getLocalHost().getHostAddress(); 
		String asyncAckLogFile = "PingAsyncAckLog." + NetUtil.getLocalHost().getHostAddress(); 
		String latencyLogFile = "LatencyLog." + NetUtil.getLocalHost().getHostAddress(); 
		if (logDir != null) { 
			senderLogFile = logDir + "/" + senderLogFile; 
			serverLogFile = logDir + "/" + serverLogFile; 
			asyncSenderLogFile = logDir + "/" + asyncSenderLogFile; 
			asyncAckLogFile = logDir + "/" + asyncAckLogFile; 
			latencyLogFile = logDir + "/" + latencyLogFile; 
		}
		System.out.println("Sender Log is saved to " + new File(senderLogFile).getAbsolutePath()); 
		System.out.println("Server Log is saved to " + new File(serverLogFile).getAbsolutePath()); 
		System.out.println("Async Sender Log is saved to " + new File(asyncSenderLogFile).getAbsolutePath()); 
		System.out.println("Async Ack Server Log is saved to " + new File(asyncAckLogFile).getAbsolutePath()); 
		System.out.println("Latency Log is saved to " + new File(latencyLogFile).getAbsolutePath());
			
		senderLogStream = new FileOutputStream(senderLogFile); 
		receiverLogStream = new FileOutputStream(serverLogFile); 
		asyncSenderStream = new FileOutputStream(asyncSenderLogFile); 
		asyncAckStream = new FileOutputStream(asyncAckLogFile); 
		latencyLogStream = new FileOutputStream(latencyLogFile); 
		
		PrintStream spStream = new PrintStream(senderLogStream); 
		spStream.println("#Start experiment at " + new Date()); 
		spStream.println("#EndPoint | Source | Session  |  Duration  |  payload  |  Sending-Rate  |  Total-Send  |  Total-Received  |  Success-rate  |  Latency  |  Timestamp\n");		
		spStream.flush(); 
		PrintStream rpStream = new PrintStream(receiverLogStream); 
		rpStream.println("#Start experiment at" + new Date()); 
		rpStream.println("#EndPoint  |  Source  |  Session  |  Packets  |  Last-Update\n"); 
		rpStream.flush(); 
		PrintStream asenderStream = new PrintStream(asyncSenderStream); 
		asenderStream.println("#Start experiment at " + new Date()); 
		asenderStream.println("#EndPoint | Source | Session  |  Duration  |  payload  |  Sending-Rate  |  Total-Send  |  Timestamp\n");		
		asenderStream.flush(); 
		PrintStream ackStream = new PrintStream(asyncAckStream); 
		ackStream.println("#Start experiment at" + new Date()); 
		ackStream.println("#EndPoint  |  Source  |  Session  |  Total-Received  |  Last-Update\n"); 
		ackStream.flush(); 

		BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 
		String line; 
		while((line = reader.readLine()) != null) { 
			line = line.trim(); 
			if (line.length() == 0 || line.startsWith("#")) { 
				continue; 
			}
			StringTokenizer tokenizer = new StringTokenizer(line);
			String host = tokenizer.nextToken(); 
			if (host.equals(localHost)) { 
				String type = tokenizer.nextToken(); 
				if (type.equals("PingSender")) { 
					parsePingSender(tokenizer); 
				} else if (type.equals("PingServer")) { 
					parsePingServer(tokenizer); 
				} else if (type.equals("PingServerExt")) { 
					parsePingServerExt(tokenizer); 
				}
				else if (type.equals("AsyncPingSender")) {
					parseAsyncPingSender(tokenizer); 
				} else if (type.equals("AsyncPingAck"))  { 
					parseAsyncPingAck(tokenizer); 
				} else if (type.equals("PingSenderTypeCastAck")) { 
					parsePingSenderAck(tokenizer);
				} else if (type.equals("DiscoverSender")) { 
					parseDiscoverSender(tokenizer); 
				} else if (type.equals("PingFuture")) { 
					parsePingFuture(tokenizer); 
				}
				else { 
					System.out.println("Unrecoganized type " + type); 
					System.exit(1); 
				}
			}
		}
	}
	
	private PingSender parsePingSender(StringTokenizer tokenizer) throws IOException { 
		String type = tokenizer.nextToken(); 
		String sessionID = tokenizer.nextToken(); 
		EndPoint endPoint = parseEndPoint(tokenizer); 
		long start = parseLong(tokenizer) * 60 * 1000; 
		long stop = parseLong(tokenizer) * 60 * 1000; 
		long duration = stop - start; 
		int rate = parseInt(tokenizer); 
		int payloadSize = parseInt(tokenizer); 
		System.out.println("Schedule PingSender for type " + type + " with Session  " + sessionID + " for delay of " + start/1000/60 + " minutes and duration of " + duration/1000/60 + " minutes"); 
		Class targetType = getType(type); 
		PingSender pingSender = new PingSender(targetType, endPoint, sessionID, start, duration, rate, payloadSize, senderLogStream, latencyLogStream); 		
		return pingSender; 
	}
	
	private PingServer parsePingServer(StringTokenizer tokenizer) throws IOException { 
		String type = tokenizer.nextToken(); 
		String sessionID = tokenizer.nextToken(); 
		EndPoint endPoint = parseEndPoint(tokenizer); 
		long start = parseLong(tokenizer) * 60 * 1000; 
		long stop = parseLong(tokenizer) * 60 * 1000; 
		long duration = stop - start; 
		System.out.println("Schedule PingServer of " + type + " for delay of " + start/1000/60 + " minutes and duration of " + duration/1000/60 + " minutes"); 		
		Class targetType = getType(type); 
		PingServer pingServer = new PingServer(targetType, endPoint, start, duration, receiverLogStream); 
		return pingServer; 
	}


	private PingServerExt parsePingServerExt(StringTokenizer tokenizer) throws IOException { 
		String type = tokenizer.nextToken(); 
		String sessionID = tokenizer.nextToken(); 
		EndPoint endPoint = parseEndPoint(tokenizer); 
		long start = parseLong(tokenizer) * 60 * 1000; 
		long stop = parseLong(tokenizer) * 60 * 1000; 
		long duration = stop - start; 
		System.out.println("Schedule PingServer of " + type + " for delay of " + start/1000/60 + " minutes and duration of " + duration/1000/60 + " minutes"); 		
		Class targetType = getType(type); 
		PingServerExt pingServer = new PingServerExt(targetType, endPoint, start, duration, receiverLogStream); 
		return pingServer; 
	}
	private PingSender parsePingSenderAck(StringTokenizer tokenizer) throws IOException { 
		String type = tokenizer.nextToken(); 
		String sessionID = tokenizer.nextToken(); 
		EndPoint endPoint = parseEndPoint(tokenizer); 
		long start = parseLong(tokenizer) * 60 * 1000; 
		long stop = parseLong(tokenizer) * 60 * 1000; 
		long duration = stop - start; 
		int rate = parseInt(tokenizer); 
		int payloadSize = parseInt(tokenizer); 
		System.out.println("Schedule PingSenderTypeCastAck for type " + type + " with Session  " + sessionID + " for delay of " + start/1000/60 + " minutes and duration of " + duration/1000/60 + " minutes"); 
		Class targetType = getType(type); 
		PingSenderTypeCastAck pingSender = new PingSenderTypeCastAck(targetType, endPoint, sessionID, start, duration, rate, payloadSize, senderLogStream, this.latencyLogStream); 		
		return pingSender; 
	}

	
	private AsyncPingSender parseAsyncPingSender(StringTokenizer tokenizer) throws IOException { 
		String type = tokenizer.nextToken(); 
		String sessionID = tokenizer.nextToken(); 
		EndPoint endPoint = parseEndPoint(tokenizer); 
		long start = parseLong(tokenizer) * 60 * 1000; 
		long stop = parseLong(tokenizer) * 60 * 1000; 
		long duration = stop - start; 
		int rate = parseInt(tokenizer); 
		int payloadSize = parseInt(tokenizer); 
		String ackString = "PingAck"; 
		Class ackType = PingAck.class; 
		if (tokenizer.hasMoreTokens()) { 
			ackString = tokenizer.nextToken(); 
			ackType = getAckType(ackString); 
		}
		System.out.println("Schedule AyncPingSender for type " + type + " to " +  ackString + " with Session  " + sessionID + " for delay of " + start/1000/60 + " minutes and duration of " + duration/1000/60 + " minutes"); 
		Class targetType = getType(type); 
		AsyncPingSender pingSender = new AsyncPingSender(targetType, ackType, endPoint, sessionID, start, duration, rate, payloadSize, this.asyncSenderStream); 		
		return pingSender; 
	}
	private AsyncPingAckServer parseAsyncPingAck(StringTokenizer tokenizer) throws IOException { 
		String type = tokenizer.nextToken(); 
		String sessionID = tokenizer.nextToken(); 
		EndPoint endPoint = parseEndPoint(tokenizer); 
		long start = parseLong(tokenizer) * 60 * 1000; 
		long stop = parseLong(tokenizer) * 60 * 1000; 
		long duration = stop - start; 
		System.out.println("Schedule AyncPingAck of " +  type + " for delay of " + start/1000/60 + " minutes and duration of " + duration/1000/60 + " minutes"); 		
		Class targetType = getAckType(type); 
		AsyncPingAckServer ackServer = new AsyncPingAckServer(targetType, endPoint, start, duration, this.asyncAckStream); 
		return ackServer; 
	}	

	private DiscoverSender parseDiscoverSender(StringTokenizer tokenizer) throws IOException { 
		String type = tokenizer.nextToken(); 
		String sessionID = tokenizer.nextToken(); 
		EndPoint endPoint = parseEndPoint(tokenizer); 
		long start = parseLong(tokenizer) * 60 * 1000; 
		long stop = parseLong(tokenizer) * 60 * 1000; 
		long duration = stop - start; 
		int rate = parseInt(tokenizer); 
		int payloadSize = parseInt(tokenizer); 
		System.out.println("Schedule DiscoverSender for type " + type + " with Session  " + sessionID + " for delay of " + start/1000/60 + " minutes and duration of " + duration/1000/60 + " minutes"); 
		Class targetType = getType(type); 
		DiscoverSender discoverSender = new DiscoverSender(targetType, endPoint, sessionID, start, duration, rate, payloadSize, senderLogStream, this.latencyLogStream); 		
		return discoverSender; 
	}

	private PingFuture parsePingFuture(StringTokenizer tokenizer) throws IOException { 
		String type = tokenizer.nextToken(); 
		String sessionID = tokenizer.nextToken(); 
		EndPoint endPoint = parseEndPoint(tokenizer); 
		long start = parseLong(tokenizer) * 60 * 1000; 
		long stop = parseLong(tokenizer) * 60 * 1000; 
		long duration = stop - start; 
		int rate = parseInt(tokenizer); 
		int payloadSize = parseInt(tokenizer); 
		System.out.println("Schedule PingFuture for type " + type + " with Session  " + sessionID + " for delay of " + start/1000/60 + " minutes and duration of " + duration/1000/60 + " minutes"); 
		Class targetType = getType(type); 
		PingFuture pingFuture = new PingFuture(targetType, endPoint, sessionID, start, duration, rate, payloadSize, senderLogStream, latencyLogStream); 		
		return pingFuture; 
	}
	
	private EndPoint parseEndPoint(StringTokenizer tokenizer) throws IOException { 
		String endPointString = tokenizer.nextToken(); 
		int index = endPointString.indexOf(":"); 
		String hostString = endPointString.substring(0, index); 
		String portString = endPointString.substring(index+1); 
		return new EndPoint(InetAddress.getByName(hostString), Integer.parseInt(portString)); 		
	}
	
	private long parseLong(StringTokenizer tokenizer) throws IOException { 
		return Long.parseLong(tokenizer.nextToken()); 
	}
	
	private int parseInt(StringTokenizer tokenizer) throws IOException { 
		return Integer.parseInt(tokenizer.nextToken()); 
	}
	
	private Class getType(String typeName) { 
		try { 
			String className = "edu.ucla.cs.typecast.test.Ping" + typeName; 
			return Class.forName(className); 
		} catch(Throwable t) { 
			t.printStackTrace(); 
			System.exit(1); 
			throw new RuntimeException(t);
		}
	}
	
	private Class getAckType(String typeName) { 
		try { 
			String className = "edu.ucla.cs.typecast.test.PingAck" + typeName; 
			return Class.forName(className); 
		} catch(Throwable t) { 
			t.printStackTrace(); 
			System.exit(1); 
			throw new RuntimeException(t);
		}
	}
	
}
